#ifdef MAODV

/*****************************************************************************
 *
 * Copyright (C) 2003 CSHCN, University of Maryland.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Authors: Dionysus Blazakis <dblaze@umd.edu>
 *          Jonathan Polley <jpolley@umd.edu>
 *          Dan Rusk <drusk@umd.edu>
 *          Manish Karir <karir@umd.edu>
 *          Jonathan McGee <mcgee@umd.edu>
 *
 *****************************************************************************/

#ifdef NS_PORT
#include "aodv-uu.h"
#else
#include <netinet/in.h>
#include "aodv_socket.h"
#include "params.h"
#include "seek_list.h"
#include "defs.h"
#include "debug.h"
#include "multicast_routing_table.h"
#include "igmp.h"
#include "icmp.h"
#include "aodv_rreq.h"
#include "maodv_mact.h"
#include "packet_queue.h"
#endif

static void maodv_join(u_int32_t grp_addr){
    mrt_table_t *group_rec;

    group_rec=mrt_table_find(grp_addr);

    if(group_rec==NULL){
        group_rec=mrt_table_insert(grp_addr, 0, 0, 0, 255, 0);
    }
   
    if(group_rec->is_gateway) {
        /* Gateway code has joined or is in the process of doing so */
        group_rec->is_member = 1;
    } else if(!group_rec->is_router){
        /* no route to group, so let's find one. */
        rreq_route_discovery(grp_addr, RREQ_JOIN, NULL, 0);
    }else if(!group_rec->is_member){
        /* I'm routing, so I just need to reactivate my membership */
        group_rec->is_member=1;
    }
}

static void maodv_leave(u_int32_t grp_addr){
    mrt_table_t *group_rec;

    group_rec=mrt_table_find(grp_addr);
    
    /* don't leave if not in the group */
    if(group_rec==NULL || !group_rec->is_router)
        return;

    /* leave group */
    group_rec->is_member=0;

    /* If gateway mode is enabled, make sure the gateway flag is set */
    if(this_host.gateway_mode) {
        group_rec->is_gateway = 1;
    }

    if(group_rec->is_gateway) {
        /* Only prune if no up or down stream */
        if((group_rec->activated_downstream_cnt == 0)
                && (group_rec->activated_upstream_cnt == 0)) {
            group_rec->am_leader = 0;
            group_rec->is_gateway = 0;
            mrt_table_stop_routing(group_rec);

            /* remove any pending RREQs (or other packets) for this group */
            seek_list_remove(seek_list_find(grp_addr));
            packet_queue_drop(grp_addr);

            /* flush all nexthops */
            mrt_nexthop_remove_all(group_rec);
        }
    } else if(group_rec->am_leader){
        if(group_rec->activated_downstream_cnt>0){
            /* do have downstreams, so make one new leader */
            mact_make_downstream_leader(group_rec);
        }
        group_rec->am_leader=0;
        mrt_table_stop_routing(group_rec);
    }else if(group_rec->activated_downstream_cnt==0){
        /* no downs, prune self and invalidate route */
        mrt_nexthop_t *upstream;

        upstream=mrt_table_get_activated_upstream(group_rec);
        if(upstream != NULL){
            mact_send(MACT_PRUNE, 1, grp_addr, upstream->addr,1);
            mrt_nexthop_deactivate(group_rec, upstream->addr);
        }
        mrt_table_stop_routing(group_rec);

        /* remove any pending RREQs (or other packets) for this group */
        seek_list_remove(seek_list_find(grp_addr));
        packet_queue_drop(grp_addr);

        /* flush all nexthops */
        mrt_nexthop_remove_all(group_rec);
    }
    /* otherwise I must keep route valid so I keep routing */
}

static void maodv_query(IGMP* pkt, int ifindex) {
    mrt_table_t *tblentry;
    int n;

    /* Iterate through all the multicast routing entries */
    for(n = 0; n < MRT_TABLESIZE; n++) {
        tblentry = mrt_tbl.tbl[n];

        while(tblentry) {
            igmp_sendreply(tblentry, ifindex);
            tblentry = tblentry->next;
        }
    }
}

void igmp_process( IGMP *pkt, int ifindex )
{
    u_int32_t grp_addr;
    DEBUG(LOG_DEBUG, 0, "IGMP packet out. Dropped.");

    grp_addr=ntohl(pkt->igmp_group.s_addr);

    switch( pkt->igmp_type ) {
        case IGMP_V1_MEMBERSHIP_REPORT:
        case IGMP_V2_MEMBERSHIP_REPORT:
            DEBUG(LOG_DEBUG, 0, "IGMP_REPORT");

            /* may not do anything if we already have a valid route. */
            maodv_join( grp_addr ); 

            break;
        case IGMP_V2_LEAVE_GROUP:
            DEBUG(LOG_DEBUG, 0, "IGMP_LEAVE");

            /* it may not actually prune, but at least un-member me. */
            maodv_leave( grp_addr ); 

            break;
        case IGMP_MEMBERSHIP_QUERY:
            DEBUG(LOG_DEBUG, 0, "IGMP_QUERY");

            /* Terrestrial multicast router is asking for list of groups */
            maodv_query( pkt, ifindex );

            break;
        default:
            DEBUG(LOG_DEBUG, 0, "IGMP_UNKNOWN [0x%x]", pkt->igmp_type );
            break;
    }
}

void igmp_sendreply(mrt_table_t* mrt, int ifindex) {
    char szBuffer[sizeof(struct iphdr) + sizeof(struct igmphdr)];
    struct iphdr* ip = (struct iphdr*)szBuffer;
    struct igmphdr* igmp = (struct igmphdr*)(szBuffer + sizeof(struct iphdr));
    int injectfd = DEV_IFINDEX(ifindex).injectfd, err;

    if(!mrt->nexthops || !mrt->is_router) {
        /* Nobody listening, so don't send */
        return;
    }

    if(injectfd != -1) {
        ip->version = IPVERSION;
        ip->ihl = sizeof(struct iphdr) >> 2;
        ip->tos = 0;
        ip->tot_len = htons(sizeof(szBuffer));
        ip->id = 1;
        ip->frag_off = 0;
        ip->ttl = 1;
        ip->protocol = IPPROTO_IGMP;
        ip->saddr = htonl(mrt->nexthops->addr);
        ip->daddr = htonl(mrt->grp_addr);
        ip->check = 0;
        ip->check = cksum((unsigned short*)ip, sizeof(struct iphdr));
        igmp->type = IGMP_V1_MEMBERSHIP_REPORT;
        igmp->code = 0;
        igmp->group = ip->daddr;
        igmp->csum = 0;
        igmp->csum = cksum((unsigned short*)igmp, sizeof(struct igmphdr));

        DEBUG(LOG_DEBUG, 0, "Injecting IGMP REPORT for %s from %s on %s",
            ip_to_str(ntohl(ip->daddr)), ip_to_str(ntohl(ip->saddr)),
            DEV_IFINDEX(ifindex).ifname);
        err = write(injectfd, szBuffer, sizeof(szBuffer));
        if(err < sizeof(szBuffer)) {
            perror("write");
        }
    }
}

#endif /* MAODV */
